#include <stdio.h>
#include <stdint.h>
#include <fstream>
#include <vector>
#include <chrono>

static std::uint64_t now() {
  return std::chrono::duration_cast<std::chrono::milliseconds>
    (std::chrono::high_resolution_clock::now().time_since_epoch()).count();
}

void dumpbin(void *buf, size_t sz, const std::string &path) {
  std::ofstream ofs(path, std::ios::out | std::ios::trunc | std::ios::binary);
  if (ofs.is_open()){
    ofs.write((const char *)buf, sz);
    // printf("# dump %s\n", path.c_str());
  }else{
    printf("open %s failed!", path.c_str());
  }
}

template<typename T>
void memset(void *buf, const T val, int sz) {
  for(int id = 0; id < sz; ++id){
    ((T*)buf)[id] = val;
  }
}
template<typename T>
void reduce(
  int m, T *src, T *dst) {
  float sum = 0.f;
  for(int id=0; id < m; ++id){
    sum += src[id];
  }
  dst[0] = sum;
}

struct Case{
  int m;
  float src_mask;
  std::string src_path;
  std::string dst_path;
  int loop;
  float avg_latency;
};

int main(int argc, char *argv[]) {
  std::vector<Case> cases;

#ifdef EVAL
  cases.push_back({1024, 1.0f, "data/c7_src.bin",  "data/c7_dst.bin", 1});
  cases.push_back({1, 1.0f, "data/c0_src.bin",  "data/c0_dst.bin", 1});
  cases.push_back({8, 1.0f, "data/c1_src.bin",  "data/c1_dst.bin", 1});
  cases.push_back({231400, 1.0f, "data/c2_src.bin",  "data/c2_dst.bin", 1});
  cases.push_back({1330167, 1.0f, "data/c3_src.bin",  "data/c3_dst.bin", 1});
  cases.push_back({2170028, 1.0f, "data/c4_src.bin",  "data/c4_dst.bin", 1});
  cases.push_back({5570917, 1.0f, "data/c5_src.bin",  "data/c5_dst.bin", 1});
#endif

#ifdef PERF
  float m_start = 100000;
  int loop = 1000;

  for(int m_id = m_start; m_id <= 10*m_start; m_id+=m_start){
      cases.push_back({m_id, 1.0f, "data/tmp_src.bin",  "data/tmp_dst.bin", loop});
  }
#endif

  for(int id = 0; id < cases.size(); ++id){
    #ifdef PERF
    if(id > 0){
        cases[id].loop = std::max(1, int(1000.0f/cases[id-1].avg_latency))*3;
    }
    #endif
    auto c = cases[id];
    const int m = c.m;
    std::string src_path = c.src_path, dst_path = c.dst_path;
    float src_mask = c.src_mask;

    float *src = new float[m];
    float *dst = new float[1];
    memset(src, src_mask, m);
    memset(dst, 0, 1);
    
    int start = now();
    for(int i = 0; i < c.loop; ++i){
      reduce(m, src, dst);
    }
    int end = now();
    cases[id].avg_latency =float(end-start)/float(c.loop);

    #ifdef EVAL
    dumpbin(src, m*sizeof(float), src_path);
    dumpbin(dst, 1*sizeof(float), dst_path);
    // printf("result %0.2f \n", dst[0]);
    printf("{\"m\": %d, \"src_path\": \"%s\", \"dst_path\": \"%s\"}\n", m, src_path.c_str(), dst_path.c_str());
    #endif

    #ifdef PERF
    {
      Case c = cases[id];
      const int m = c.m, loop = c.loop;
      float avg_latency = c.avg_latency;
      printf("# m %d loop %d avg_latency %0.4f ms\n", m, loop, avg_latency);
    }
    #endif

    delete []src;
    delete []dst;
  }
  return 0;
}